package com.hucsmn.authentication;

import java.io.*;
import java.net.*;
import java.util.concurrent.*;

/**
 * 用户信息认证服务器。
 */
public class AuthServer implements Runnable {
    /**
     * 默认绑定地址 0.0.0.0。
     */
    public static final InetAddress ADDRESS;

    /**
     * 默认绑定端口 8888。
     */
    public static final int PORT = 8888;

    /**
     * 默认线程池大小。
     */
    public static final int THREADS = Runtime.getRuntime().availableProcessors();

    /**
     * 套接字超时时间。
     */
    private static final int TIMEOUT = 10000;

    /**
     * 允许认证的用户。
     */
    private static final User PERMIT = new User("admin", "123456");

    static {
        InetAddress address = null;
        try {
            address = Inet4Address.getByAddress(new byte[]{0, 0, 0, 0});
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        ADDRESS = address;
    }

    private InetAddress address;
    private int port;
    private int threads;
    private ExecutorService pool = null;

    /**
     * 新建认证服务器，使用默认参数。
     */
    AuthServer() {
        this(ADDRESS, PORT, THREADS);
    }

    /**
     * 新建认证服务器，并指定监听的端口，其他参数使用默认值。
     *
     * @param port 监听的端口。
     */
    AuthServer(int port) {
        this(ADDRESS, port, THREADS);
    }

    /**
     * 新建认证服务器，并指定监听的端口和地址，其他参数使用默认值。
     *
     * @param address 监听的地址。
     * @param port 监听的端口。
     */
    AuthServer(InetAddress address, int port) {
        this(address, port, THREADS);
    }

    /**
     * 新建认证服务器，并指定监听的端口和地址，以及线程池大小。
     *
     * @param address 监听的地址。
     * @param port 监听的端口。
     * @param threads 线程池大小。
     */
    AuthServer(InetAddress address, int port, int threads) {
        setAddress(address);
        setPort(port);
        setThreads(threads);
    }

    /**
     * 开始监听并处理请求。
     */
    @Override
    public void run() {
        try (ServerSocket sock = new ServerSocket()) {
            sock.bind(new InetSocketAddress(address, port));
            if (pool == null) {
                pool = Executors.newFixedThreadPool(threads);
            }
            while (true) {
                Socket request = sock.accept();
                System.out.println("接收到 " + request.getRemoteSocketAddress() + " 的请求");
                pool.execute(() -> authenticate(request));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理用户信息认证逻辑。
     *
     * @param request 客户端发起的请求。
     */
    private void authenticate(Socket request) {
        try {
            request.setSoTimeout(TIMEOUT);

            ObjectInputStream in = new ObjectInputStream(request.getInputStream());
            Object obj = in.readObject();
            if (!(obj instanceof UserMessage)) {
                throw new InvalidObjectException("not a user message");
            }
            UserMessage um = (UserMessage) obj;
            if (!um.validate() || !UserMessage.CHECK.equals(um.getType())) {
                throw new InvalidObjectException("not a valid user message request");
            }

            if (lookup(um.getUser())) {
                System.out.println(request.getRemoteSocketAddress() + " 认证成功：" + um.getUser());
                um.setType(UserMessage.SUCCESS);
            } else {
                System.out.println(request.getRemoteSocketAddress() + " 认证失败：" + um.getUser());
                um.setType(UserMessage.FAIL);
            }

            ObjectOutputStream out = new ObjectOutputStream(request.getOutputStream());
            out.writeObject(um);
            out.flush();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 查找认证用户信息。
     *
     * @param user 用户登录信息。
     * @return 是否存在用户登录信息的记录。
     */
    private boolean lookup(User user) {
        return PERMIT.equals(user);
    }

    /**
     * 获取监听地址。
     *
     * @return 监听地址。
     */
    public InetAddress getAddress() {
        return address;
    }

    /**
     * 修改监听地址。
     *
     * @param address 监听地址。
     */
    private void setAddress(InetAddress address) {
        if (address == null) {
            throw new NullPointerException();
        }
        this.address = address;
    }

    /**
     * 获取监听端口号。
     *
     * @return 监听端口号。
     */
    public int getPort() {
        return port;
    }

    /**
     * 修改监听端口号。
     *
     * @param port 监听端口号。
     */
    private void setPort(int port) {
        if (port <= 0) {
            throw new IllegalArgumentException();
        }
        this.port = port;
    }

    /**
     * 获取线程池大小。
     *
     * @return 程池大小。
     */
    public int getThreads() {
        return threads;
    }

    /**
     * 重新设置线程池大小。
     *
     * @param threads 线程池大小。
     */
    private void setThreads(int threads) {
        if (threads <= 0) {
            throw new IllegalArgumentException();
        }
        this.threads = threads;
        resetThreadPool();
    }

    /**
     * 重置线程池。
     */
    private void resetThreadPool() {
        if (pool != null) {
            pool.shutdown();
            pool = null;
        }
    }
}
